package com.tianji.learning.utils;

import com.tianji.common.utils.JsonUtils;
import com.tianji.learning.entity.po.LearningLesson;
import com.tianji.learning.entity.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.entity.data.RecordCacheData;
import com.tianji.learning.entity.data.RecordTaskData;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;

@Component
@RequiredArgsConstructor
@Slf4j
public class LearningRecordDelayTaskHandler {

    private final StringRedisTemplate stringRedisTemplate;
    private final LearningRecordMapper learningRecordMapper;
    private final ILearningLessonService learningLessonService;

    //初始化延迟队列
    private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>();
    private final static String RECORD_KEY_TEMPLATE = "learning:record:";

    private static volatile boolean begin = true;

    /**
     * 服务启动时，初始化延迟任务
     * 当前类实例化 属性注入完成后，执行该方法  就是上面的 stringRedisTemplate learningRecordMapper learningLessonService等等
     * 被管理后和这个就开始执行了
     * 一般用来做一些初始化的操作
     */
    @PostConstruct
    public void init() {
        log.info("延迟任务开始执行！");
        // 异步延迟任务
        CompletableFuture.runAsync(this::handleDelayTask);
    }

    /**
     * 服务停止时，停止执行延迟任务
     */
    @PreDestroy
    public void destroy() {
        begin = false;
        log.debug("延迟任务停止执行！");
    }

    /**
     * 没隔15s
     */
    public void handleDelayTask() {
        while (begin) {


            try {
                // 1.获取到期的延迟任务
                DelayTask<RecordTaskData> task = queue.take();
                // 2.执行延迟任务
                RecordTaskData data = task.getData();
                Long lessonId = data.getLessonId();
                Long sectionId = data.getSectionId();
                // 查询redis里面
                LearningRecord cacheRecord = readRecordCache(data.getLessonId(), data.getSectionId());

                log.info("开始执行延迟任务,任务数据：{}, 缓存中的数据：{}", data, cacheRecord);
                if (cacheRecord == null) {
                    // 没有学习记录
                    continue;
                }
                // 3.比较redis中的学习记录和延迟任务中的学习记录是否一致
                Integer moment = data.getMoment();
                Integer cacheRecordMoment = cacheRecord.getMoment();
                if (!Objects.equals(moment, cacheRecordMoment)) {
                    // 不一致，说明已经有新地学习记录了，不需要更新
                    // 说明还在看，先不忙存入数据库
                    continue;
                }

                // 4.一致，持久化播放进度数据到数据库
                // 4.1.更新学习记录的moment
                // 这个finished必须设为null,因为当时在判断他是否学完一半的时候就更新了finished这个字段了
                cacheRecord.setFinished(null);
                learningRecordMapper.updateById(cacheRecord);
                // 4.2.更新课表最近学习信息
                LearningLesson lesson = new LearningLesson();
                lesson.setId(data.getLessonId());
                lesson.setLatestSectionId(data.getSectionId());
                lesson.setLatestLearnTime(LocalDateTime.now());
                learningLessonService.updateById(lesson);


            } catch (InterruptedException e) {
                log.error("延迟任务执行失败！", e);
            }
        }
    }

    /**
     * 添加学习记录到延迟队列 并且放入缓存
     *
     * @param record
     */
    public void addLearningRecordToDelayQueue(LearningRecord record) {
        // TODO 放入缓存
        // 为什么要放入redis里面因为我们下面的延迟任务就是和下一次的redis的数据进行比较
        // 因为我们是15秒提交一次到redis
        // 我们的延迟任务是20秒执行一次
        // 因此下一次从延迟队列的中取出来的数据比对的就是下一次的redis的数据
        writeRecordCache(record);

        // 1.构建延迟任务
        RecordTaskData data = new RecordTaskData(record);
        // 20s后执行
        DelayTask<RecordTaskData> task = new DelayTask<>(data, Duration.ofSeconds(20));
        // 2.将延迟任务添加到延迟队列中
        queue.add(task);
    }

    /**
     * 将学习记录写入redis缓存
     *
     * @param record
     */
    public void writeRecordCache(LearningRecord record) {
        log.debug("更新学习记录的缓存数据");
        try {
            // 1.数据转换
            String json = JsonUtils.toJsonStr(new RecordCacheData(record));
            // 2.写入Redis
            String key = RECORD_KEY_TEMPLATE + record.getLessonId();
            stringRedisTemplate.opsForHash().put(key, record.getSectionId().toString(), json);
            // 3.添加缓存过期时间
            stringRedisTemplate.expire(key, Duration.ofMinutes(1));
        } catch (Exception e) {
            log.error("更新学习记录缓存异常", e);
        }
    }

    /**
     * 清除学习记录的缓存
     *
     * @param lessonId
     * @param sectionId
     */
    public void cleanRecordCache(Long lessonId, Long sectionId) {
        // 删除数据
        String key = RECORD_KEY_TEMPLATE + lessonId;
        stringRedisTemplate.opsForHash().delete(key, sectionId.toString());
    }


    /**
     * 根据课程id和章节id查询学习记录在redis中的key
     * 读取缓存
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    public LearningRecord readRecordCache(Long lessonId, Long sectionId) {
        String key = RECORD_KEY_TEMPLATE + lessonId;
        // 也就是hash结构
        Object cache = stringRedisTemplate.opsForHash().get(key, sectionId.toString());
        if (cache == null) {
            return null;
        }
        // 下面进行反序列化
        return JsonUtils.toBean(cache.toString(), LearningRecord.class);
    }
}